/*
 *
 *   Copyright 2007-2008 University Of Southern California
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing,
 *   software distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
package edu.isi.pegasus.planner.catalog.site.classes;

import edu.isi.pegasus.common.util.Currently;
import edu.isi.pegasus.planner.catalog.site.classes.Directory.TYPE;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Prints the Site Catalog compatible with Site Catalog schema version 3
 * 
 * https://pegasus.isi.edu/wms/docs/schemas/sc-4.0/sc-3.0.html
 * 
 * @author Rajiv Mayani
 * @version $Revision: 5858 $
 */
public class XML3PrintVisitor extends AbstractXMLPrintVisitor {

	/**
	 * The "official" namespace URI of the site catalog schema.
	 */
	public static final String SCHEMA_NAMESPACE = "http://pegasus.isi.edu/schema/sitecatalog";

	/**
	 * The "not-so-official" location URL of the DAX schema definition.
	 */
	public static final String SCHEMA_LOCATION = "http://pegasus.isi.edu/schema/sc-3.0.xsd";

	/**
	 * The version to report.
	 */
	public static final String SCHEMA_VERSION = "3.0";

	/**
	 * 
	 * @author mayani
	 */
	private enum DirectoryTypes {
		HEADFS_SCRATCH, HEADFS_STORAGE, WORKERFS_SCRATCH, WORKERFS_STORAGE;
	}

	/**
	 * Keep track of which directories are found in the site.
	 */
	private Directory[] mDirectory = null;

	/**
	 * Keep track of which file servers are listed in the specified directories.
	 */
	private List<FileServer>[] mFileServer = null;

	/**
	 * Track which type of directory was traversed last.
	 */
	private TYPE mLastDirectoryTraversed;

	/**
	 * Ensure that Directory information is written only once.
	 */
	private boolean isFSWritten = false;

	/**
	 * Visit the SiteStore object
	 * 
	 * @param store
	 *            the site store
	 */
	public void visit(SiteStore store) throws IOException {
		String indent = getCurrentIndent();

		// write out the xml element
		mWriter.write(indent);
		indent = (indent != null && indent.length() > 0) ? indent : "";

		mWriter.write(indent);
		mWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		mWriter.write(mNewLine);

		mWriter.write(indent);
		mWriter.write("<!-- generated: ");
		mWriter.write(Currently.iso8601(false));
		mWriter.write(" -->");
		mWriter.write(mNewLine);

		// who generated this document
		mWriter.write(indent);
		mWriter.write("<!-- generated by: ");
		mWriter.write(System.getProperties()
				.getProperty("user.name", "unknown"));
		mWriter.write(" [");
		mWriter.write(System.getProperties().getProperty("user.region", "??"));
		mWriter.write("] -->");
		mWriter.write(mNewLine);

		// root element with elementary attributes
		mWriter.write(indent);
		mWriter.write('<');

		mWriter.write("sitecatalog xmlns");
		mWriter.write("=\"");
		mWriter.write(SCHEMA_NAMESPACE);
		mWriter.write("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"");
		mWriter.write(SCHEMA_NAMESPACE);
		mWriter.write(' ');
		mWriter.write(SCHEMA_LOCATION);
		mWriter.write('"');
		writeAttribute(mWriter, "version", SCHEMA_VERSION);
		mWriter.write('>');
		mWriter.write(mNewLine);
		
		incrementIndentIndex();
	}

	/**
	 * Depart the Site Store object.
	 * 
	 * @param store
	 *            the SiteStore
	 */
	public void depart(SiteStore store) throws IOException {
		decrementIndentIndex();
		closeElement("sitecatalog");
	}

	/**
	 * Visit the Site CatalogEntry object
	 * 
	 * @param entry
	 *            the site catalog entry
	 * 
	 * @throws IOException
	 *             in case of problem of writing
	 */
	public void visit(SiteCatalogEntry entry) throws IOException {
		String indent = getCurrentIndent();

		// write out the xml element
		mWriter.write(indent);
		mDirectory = new Directory[4];
		mFileServer = new List[4];
		isFSWritten = false;

		mWriter.write("<site ");
		writeAttribute(mWriter, "handle", entry.getSiteHandle());
		writeAttribute(mWriter, "arch", entry.getArchitecture().toString());
		writeAttribute(mWriter, "os", entry.getOS().toString());

		String val = null;
		if ((val = entry.getOSRelease()) != null && val.length() > 0) {
			writeAttribute(mWriter, "osrelease", val);
		}

		if ((val = entry.getOSVersion()) != null && val.length() > 0) {
			writeAttribute(mWriter, "osversion", val);
		}

		if ((val = entry.getGlibc()) != null && val.length() > 0) {
			writeAttribute(mWriter, "glibc", val);
		}

		mWriter.write(">");
		mWriter.write(mNewLine);

		// for our nested elements we have to increment the index
		incrementIndentIndex();

	}

	/**
	 * Depart the Site Catalog Entry object.
	 * 
	 * @param entry
	 *            the site catalog entry
	 */
	public void depart(SiteCatalogEntry entry) throws IOException {
		String indent = getCurrentIndent();

		if (!isFSWritten) {
			writeFS();
			isFSWritten = true;
		}

		entry.getProfiles().toXML(mWriter, indent);

		closeElement("site");

	}

	private void writeFS() throws IOException {
		writeHeadFS();
		writeWorkerFS();
	}

	private void writeHeadFS() throws IOException {
		if (mDirectory[DirectoryTypes.HEADFS_STORAGE.ordinal()] != null
				|| mDirectory[DirectoryTypes.HEADFS_SCRATCH.ordinal()] != null
				|| mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()] != null) {

			String indent = getCurrentIndent();

			mWriter.write(indent);
			mWriter.write("<head-fs>");
			mWriter.write(mNewLine);
			incrementIndentIndex();

			writeHeadFSScratch();
			writeHeadFSStorage();

			decrementIndentIndex();
			mWriter.write(indent);
			mWriter.write("</head-fs>");
			mWriter.write(mNewLine);
		}
	}

	private void writeHeadFSScratch() throws IOException {
		String indent = getCurrentIndent();

		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<scratch>");
		mWriter.write(mNewLine);

		Directory directory = mDirectory[DirectoryTypes.HEADFS_SCRATCH
				.ordinal()];

		incrementIndentIndex();
		indent = getCurrentIndent();
		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<shared>");
		mWriter.write(mNewLine);

		incrementIndentIndex();
		indent = getCurrentIndent();
		writeFileServer(directory, DirectoryTypes.HEADFS_SCRATCH);
		// write out the internal mount point
		directory.getInternalMountPoint().toXML(mWriter, indent);
		decrementIndentIndex();
		indent = getCurrentIndent();

		mWriter.write(indent);
		mWriter.write("</shared>");
		mWriter.write(mNewLine);

		decrementIndentIndex();
		indent = getCurrentIndent();

		mWriter.write(indent);
		mWriter.write("</scratch>");
		mWriter.write(mNewLine);
	}

	private void writeFileServer(Directory directory, DirectoryTypes type)
			throws IOException {
		String indent = getCurrentIndent();

		// iterate through all the file servers
		List<FileServer> servers = mFileServer[type.ordinal()];

		for (FileServer server : servers) {
			// write out the xml element
			mWriter.write(indent);
			mWriter.write("<file-server");

			writeAttribute(mWriter, "protocol", server.getProtocol());
			writeAttribute(mWriter, "url", server.getURLPrefix());
			writeAttribute(mWriter, "mount-point", server.getMountPoint());

			if (server.getProfiles().isEmpty()) {
				mWriter.write("/>");
			} else {
				mWriter.write(">");
				mWriter.write(mNewLine);

				incrementIndentIndex();
				indent = getCurrentIndent();
				server.getProfiles().toXML(mWriter, indent);
				decrementIndentIndex();
				indent = getCurrentIndent();

				mWriter.write(indent);
				mWriter.write("</file-server>");
			}
			mWriter.write(mNewLine);
		}
	}

	private void writeHeadFSStorage() throws IOException {
		String indent = getCurrentIndent();

		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<storage>");
		mWriter.write(mNewLine);

		Directory directory = mDirectory[DirectoryTypes.HEADFS_STORAGE
				.ordinal()];

		if (directory != null) {
			incrementIndentIndex();
			indent = getCurrentIndent();
			// write out the xml element
			mWriter.write(indent);
			mWriter.write("<shared>");
			mWriter.write(mNewLine);

			incrementIndentIndex();
			indent = getCurrentIndent();
			writeFileServer(directory, DirectoryTypes.HEADFS_STORAGE);
			// write out the internal mount point
			directory.getInternalMountPoint().toXML(mWriter, indent);
			decrementIndentIndex();
			indent = getCurrentIndent();

			mWriter.write(indent);
			mWriter.write("</shared>");
			mWriter.write(mNewLine);

			decrementIndentIndex();
			indent = getCurrentIndent();
		}

		// Local
		directory = mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()];

		if (directory != null) {
			incrementIndentIndex();
			indent = getCurrentIndent();
			// write out the xml element
			mWriter.write(indent);
			mWriter.write("<local>");
			mWriter.write(mNewLine);

			incrementIndentIndex();
			indent = getCurrentIndent();
			writeFileServer(directory, DirectoryTypes.WORKERFS_STORAGE);
			// write out the internal mount point
			directory.getInternalMountPoint().toXML(mWriter, indent);
			decrementIndentIndex();
			indent = getCurrentIndent();

			mWriter.write(indent);
			mWriter.write("</local>");
			mWriter.write(mNewLine);

			decrementIndentIndex();
			indent = getCurrentIndent();
		}

		mWriter.write(indent);
		mWriter.write("</storage>");
		mWriter.write(mNewLine);
	}

	private void writeWorkerFS() throws IOException {
		String indent = getCurrentIndent();

		Directory directory = mDirectory[DirectoryTypes.WORKERFS_SCRATCH
				.ordinal()];

		if (directory != null) {
			// write out the xml element
			mWriter.write(indent);
			mWriter.write("<worker-fs>");
			mWriter.write(mNewLine);

			incrementIndentIndex();
			writeWorkerFSScratch();
			decrementIndentIndex();

			mWriter.write(indent);
			mWriter.write("</worker-fs>");
			mWriter.write(mNewLine);
		}
	}

	private void writeWorkerFSScratch() throws IOException {
		String indent = getCurrentIndent();
		Directory directory = mDirectory[DirectoryTypes.WORKERFS_SCRATCH
				.ordinal()];

		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<scratch>");
		mWriter.write(mNewLine);

		incrementIndentIndex();
		indent = getCurrentIndent();
		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<local>");
		mWriter.write(mNewLine);

		incrementIndentIndex();
		indent = getCurrentIndent();
		writeFileServer(directory, DirectoryTypes.WORKERFS_SCRATCH);
		// write out the internal mount point
		directory.getInternalMountPoint().toXML(mWriter, indent);
		decrementIndentIndex();
		indent = getCurrentIndent();

		mWriter.write(indent);
		mWriter.write("</local>");
		mWriter.write(mNewLine);

		decrementIndentIndex();
		indent = getCurrentIndent();

		mWriter.write(indent);
		mWriter.write("</scratch>");
		mWriter.write(mNewLine);
	}

	/**
	 * Visit the GridGateway object
	 * 
	 * @param gateway
	 *            the grid gateway
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void visit(GridGateway gateway) throws IOException {
		String indent = getCurrentIndent();

		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<grid ");
		writeAttribute(mWriter, "type", gateway.getType().toString());
		writeAttribute(mWriter, "contact", gateway.getContact());
		writeAttribute(mWriter, "scheduler", gateway.getScheduler().toString());
		writeAttribute(mWriter, "jobtype", gateway.getJobType().toString());

		if (gateway.getOS() != null) {
			writeAttribute(mWriter, "os", gateway.getOS().toString());
		}
		if (gateway.getArchitecture() != null) {
			writeAttribute(mWriter, "arch", gateway.getArchitecture()
					.toString());
		}

		String val = null;
		if ((val = gateway.getOSRelease()) != null) {
			writeAttribute(mWriter, "osrelease", val);
		}

		if ((val = gateway.getOSVersion()) != null) {
			writeAttribute(mWriter, "osversion", val);
		}

		if ((val = gateway.getGlibc()) != null) {
			writeAttribute(mWriter, "glibc", val);
		}

		if (gateway.getIdleNodes() != -1) {
			writeAttribute(mWriter, "idle-nodes",
					Integer.toString(gateway.getIdleNodes()));
		}

		if (gateway.getTotalNodes() != -1) {
			writeAttribute(mWriter, "total-nodes",
					Integer.toString(gateway.getTotalNodes()));
		}

		mWriter.write("/>");
		mWriter.write(mNewLine);
	}

	/**
	 * Depart the GridGateway object
	 * 
	 * @param entry
	 *            GridGateway object
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void depart(GridGateway entry) throws IOException {

	}

	/**
	 * Visit the directory object
	 * 
	 * @param directory
	 *            the directory
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void visit(Directory directory) throws IOException {
		// sanity check?
		if (directory.isEmpty()) {
			return;
		}

		switch (directory.getType()) {
		case shared_scratch:
			mLastDirectoryTraversed = TYPE.shared_scratch;
			mDirectory[DirectoryTypes.HEADFS_SCRATCH.ordinal()] = directory;
			break;
		case shared_storage:
			mLastDirectoryTraversed = TYPE.shared_storage;
			mDirectory[DirectoryTypes.HEADFS_STORAGE.ordinal()] = directory;
			break;
		case local_scratch:
			mLastDirectoryTraversed = TYPE.local_scratch;
			mDirectory[DirectoryTypes.WORKERFS_SCRATCH.ordinal()] = directory;
			break;
		case local_storage:
			mLastDirectoryTraversed = TYPE.local_storage;
			mDirectory[DirectoryTypes.WORKERFS_STORAGE.ordinal()] = directory;
			break;
		default:
			break;
		}
	}

	/**
	 * Depart the shared directory
	 * 
	 * @param directory
	 *            the directory
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void depart(Directory directory) throws IOException {
	}

	/**
	 * Visit FileServer site data object
	 * 
	 * @param server
	 *            the object corresponding to the FileServer
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void visit(FileServer server) throws IOException {

		if (mLastDirectoryTraversed == null) {
			// Should never happen
		}

		if (mFileServer[mLastDirectoryTraversed.ordinal()] == null) {
			mFileServer[mLastDirectoryTraversed.ordinal()] = new ArrayList<FileServer>();
		}

		mFileServer[mLastDirectoryTraversed.ordinal()].add(server);
	}

	/**
	 * Depart the Directory object
	 * 
	 * @param server
	 *            the object corresponding to the FileServer
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void depart(FileServer server) throws IOException {
	}

	/**
	 * Visit the ReplicaCatalog object
	 * 
	 * @param catalog
	 *            the object describing the catalog
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void visit(ReplicaCatalog catalog) throws IOException {
		String indent = this.getCurrentIndent();

		if (!isFSWritten) {
			writeFS();
			isFSWritten = true;
		}

		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<replica-catalog ");

		// fixed for time being
		writeAttribute(mWriter, "type", catalog.getType());
		writeAttribute(mWriter, "url", catalog.getURL());

		mWriter.write(">");
		mWriter.write(mNewLine);

		// for our nested elements we have to increment the index
		incrementIndentIndex();
	}

	/**
	 * Depart the ReplicaCatalog object
	 * 
	 * @param catalog
	 *            the object describing the catalog
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void depart(ReplicaCatalog catalog) throws IOException {
		String indent = getCurrentIndent();

		// list all the aliases first
		for (Iterator<String> it = catalog.getAliasIterator(); it.hasNext();) {
			catalog.writeAlias(mWriter, indent, it.next());

		}

		closeElement("replica-catalog");
	}

	/**
	 * Visit the connection object
	 * 
	 * @param c
	 *            the connection.
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void visit(Connection c) throws IOException {
		String indent = this.getCurrentIndent();

		// write out the xml element
		// write out the xml element
		mWriter.write(indent);
		mWriter.write("<connection ");

		writeAttribute(mWriter, "key", c.getKey());

		mWriter.write(">");
		mWriter.write(c.getValue());
		mWriter.write("</connection>");
		mWriter.write(mNewLine);

		// for our nested elements we have to increment the index
		incrementIndentIndex();
	}

	/**
	 * Depart the connection object
	 * 
	 * @param c
	 *            the connection.
	 * 
	 * @throws IOException
	 *             in case of error while writing to underlying stream
	 */
	public void depart(Connection c) throws IOException {
		closeElement("connection");
	}
}